package spring.security.core.util;

import java.io.IOException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import com.google.common.base.Strings;

import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Request.Builder;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.Route;
import okhttp3.internal.Util;
import spring.security.core.support.HttpMethod;
import spring.security.core.support.LoggingInterceptor;

public class HttpClientUtils {
	
	private static final Interceptor loggingInterceptor = new LoggingInterceptor();
	public static final Charset DEFAULT_UTF8_CHARSET;
	public static final String JSON_CONTENT_TYPE = "application/json; charset=UTF-8";
	public static final String XML_CONTENT_TYPE = "application/xml; charset=UTF-8";
	private static final TrustManager[] trustAllCerts;
	private static final X509TrustManager trustManager;
	private static final HostnameVerifier DO_NOT_VERIFY;
	public static final OkHttpClient DEFAULT_HTTP_CLIENT;
	public static final OkHttpClient DEFAULT_HTTPS_CLIENT;

	private static SSLSocketFactory getTrustedSSLSocketFactory() {
		try {
			SSLContext e = SSLContext.getInstance("SSL");
			e.init((KeyManager[]) null, trustAllCerts, new SecureRandom());
			return e.getSocketFactory();
		} catch (NoSuchAlgorithmException | KeyManagementException arg0) {
			arg0.printStackTrace();
			return null;
		}
	}

	public static String requestByJson(String url, HttpMethod httpMethod, String bodyContent) throws IOException {
		return requestByJson(url, httpMethod, null, null, bodyContent);
	}

	public static String requestByJson(String url, HttpMethod httpMethod, Map<String, String> headers,
			String bodyContent) throws IOException {
		return requestByJson(url, httpMethod, headers, null, bodyContent);
	}

	public static String requestByJson(String url, HttpMethod httpMethod, Map<String, String> headers,
			Map<String, Object> formData, String bodyContent) throws IOException {
		return request((OkHttpClient) null, url, httpMethod, "application/json; charset=UTF-8", headers, formData,
				bodyContent);
	}

	public static String request(String url, HttpMethod httpMethod) throws IOException {
		return request(url, httpMethod, null);
	}

	public static String request(String url, HttpMethod httpMethod, Map<String, Object> formData) throws IOException {
		return request(url, httpMethod, null, formData);
	}

	public static String request(String url, HttpMethod httpMethod, Map<String, String> headers,
			Map<String, Object> formData) throws IOException {
		return request((OkHttpClient) null, url, httpMethod, "application/json; charset=UTF-8", headers, formData,
				(String) null);
	}

	public static String request(OkHttpClient client, String url, HttpMethod httpMethod, String mediaType,
			Map<String, String> headers, Map<String, Object> formData, String bodyContent) throws IOException {
		Builder builder = new Builder();
		if (httpMethod == HttpMethod.GET) {
			url = MapUtils.getUrlParamsByMap(url, formData, true);
		} else {
			RequestBody request = buildParams(formData);
			if (setRequestBody(httpMethod, request, mediaType, bodyContent, builder)) {
				url = MapUtils.getUrlParamsByMap(url, formData, true);
			}
		}

		setRequestHeader(headers, builder);
		Request request1 = builder.url(url).build();
		if (client == null) {
			if (request1.isHttps()) {
				client = DEFAULT_HTTPS_CLIENT;
			} else {
				client = DEFAULT_HTTP_CLIENT;
			}
		}

		Response res = client.newCall(request1).execute();
		return res.body().source().readUtf8();
	}

	private static RequestBody buildParams(Map<String, Object> formData) {
		if (formData != null && !formData.isEmpty()) {
			okhttp3.FormBody.Builder formBuilder = new okhttp3.FormBody.Builder();
			formData.forEach((k, v) -> {
				if (v != null) {
					formBuilder.add(k, "" + v);
				}

			});
			return formBuilder.build();
		} else {
			return null;
		}
	}

	private static boolean setRequestBody(HttpMethod httpMethod, RequestBody formDataBody, String mediaType,
			String bodyContent, Builder builder) {
		if (bodyContent != null && !Strings.isNullOrEmpty(mediaType)) {
			RequestBody body = RequestBody.create(MediaType.parse(mediaType),
					bodyContent.getBytes(DEFAULT_UTF8_CHARSET));
			switch (httpMethod.ordinal()) {
			case 1:
				builder.delete(body);
				break;
			case 2:
				builder.post(body);
				break;
			case 3:
				builder.put(body);
			case 4:
			}

			return true;
		} else {
			switch (httpMethod.ordinal()) {
			case 1:
				builder.delete(formDataBody);
				break;
			case 2:
				builder.post(formDataBody);
				break;
			case 3:
				builder.put(formDataBody);
			case 4:
			}

			return false;
		}
	}

	private static void setRequestHeader(Map<String, String> headers, Builder builder) {
		if (headers != null && !headers.isEmpty()) {
			headers.forEach((k, v) -> {
				if (k != null && v != null) {
					builder.addHeader(k, v);
				}

			});
		}
	}

	public static OkHttpClient okHttpClient() {
		return okHttpClient(180, 180, 180, false, false, null);
	}

	public static OkHttpClient okHttpClient(int connectTimeout, int readTimeout, int writeTimeout, boolean ssl) {
		return okHttpClient(connectTimeout, readTimeout, writeTimeout, ssl, false, null);
	}

	public static OkHttpClient okHttpClient(int connectTimeout, int readTimeout, int writeTimeout, boolean ssl,
			boolean retryOnConnectionFailure) {
		return okHttpClient(connectTimeout, readTimeout, writeTimeout, ssl, retryOnConnectionFailure, null);
	}

	public static OkHttpClient okHttpClient(int connectTimeout, int readTimeout, int writeTimeout, boolean ssl,
			boolean retryOnConnectionFailure, final Map<String, String> headerMap) {
		okhttp3.OkHttpClient.Builder builder = new okhttp3.OkHttpClient.Builder();
		builder = builder.connectTimeout((long) connectTimeout, TimeUnit.SECONDS)
				.readTimeout((long) readTimeout, TimeUnit.SECONDS).writeTimeout((long) writeTimeout, TimeUnit.SECONDS);
		if (ssl) {
			builder = builder.sslSocketFactory(getTrustedSSLSocketFactory(), trustManager)
					.hostnameVerifier(DO_NOT_VERIFY);
		}

		if (retryOnConnectionFailure) {
			builder = builder.retryOnConnectionFailure(retryOnConnectionFailure);
		}

		if (headerMap != null && !headerMap.isEmpty()) {
			builder = builder.addInterceptor(new Interceptor() {
				public Response intercept(Chain chain) throws IOException {
					Request originalRequest = chain.request();
					okhttp3.Headers.Builder headerBuidler = new okhttp3.Headers.Builder();
					headerMap.forEach((k, v) -> {
						headerBuidler.add(k, v);
					});
					Request requestWithUserAgent = originalRequest.newBuilder().headers(headerBuidler.build()).build();
					return chain.proceed(requestWithUserAgent);
				}
			});
		}

		builder.authenticator(new Authenticator() {
			public Request authenticate(Route route, Response response) throws IOException {
				if (HttpClientUtils.responseCount(response) >= 3) {
					return null;
				} else {
					String credential = Credentials.basic("name", "password");
					return response.request().newBuilder().header("Authorization", credential).build();
				}
			}
		});
		builder.addInterceptor(loggingInterceptor);
		return builder.build();
	}

	private static int responseCount(Response response) {
		int result;
		for (result = 1; (response = response.priorResponse()) != null; ++result) {
			;
		}

		return result;
	}

	static {
		DEFAULT_UTF8_CHARSET = Util.UTF_8;
		trustAllCerts = new TrustManager[] { new X509TrustManager() {
			public X509Certificate[] getAcceptedIssuers() {
				X509Certificate[] x509Certificates = new X509Certificate[0];
				return x509Certificates;
			}

			public void checkClientTrusted(X509Certificate[] certs, String authType) {
			}

			public void checkServerTrusted(X509Certificate[] certs, String authType) {
			}
		} };
		trustManager = new X509TrustManager() {
			public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			}

			public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			}

			public X509Certificate[] getAcceptedIssuers() {
				return new X509Certificate[0];
			}
		};
		DO_NOT_VERIFY = new HostnameVerifier() {
			public boolean verify(String hostname, SSLSession session) {
				return true;
			}
		};
		DEFAULT_HTTP_CLIENT = okHttpClient(180, 180, 180, false, true, null);
		DEFAULT_HTTPS_CLIENT = okHttpClient(180, 180, 180, true, true, null);
	}
	
}
